/*
 * ******************************************************************************
 *   Copyright (c) 2014 Gabriele Mariotti.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *  *****************************************************************************
 */

package it.gmariotti.cardslib.library.internal.dismissanimation;


import it.gmariotti.cardslib.library.AnimatorListenerAdapter;
import it.gmariotti.cardslib.library.internal.base.BaseCardArrayAdapter;
import it.gmariotti.cardslib.library.internal.Card;
import it.gmariotti.cardslib.library.internal.CardArrayAdapter;
import it.gmariotti.cardslib.library.view.CardListView;
import it.gmariotti.cardslib.library.view.base.CardViewWrapper;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ListContainer;
import ohos.app.Context;
import ohos.miscservices.timeutility.Time;

import java.util.*;

/**
 * @author Gabriele Mariotti (gabri.mariotti@gmail.com)
 */
public abstract class BaseDismissAnimation {

    protected BaseCardArrayAdapter mBaseAdapter;
    protected CardListView mCardListView;
    protected int mListWidth;
    protected int mAnimationTime = 200;
    protected boolean mDismissRight = true;
    protected Context mContext;


    private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
    private int mDownPosition;
    private int mDismissAnimationRefCount = 0;

    //--------------------------------------------------------------------------
    // Constructor
    //--------------------------------------------------------------------------

    public BaseDismissAnimation(Context context) {
        mContext = context;
    }

    //--------------------------------------------------------------------------
    // Init
    //--------------------------------------------------------------------------

    public BaseDismissAnimation setup(BaseCardArrayAdapter adapter) {
        mBaseAdapter = adapter;
        return this;
    }

    //--------------------------------------------------------------------------
    // Callbacks
    //--------------------------------------------------------------------------

    /**
     * The callback interface used by {@link } to inform its client
     * about a successful dismissal of one or more list item positions.
     */
    public interface DismissCallbacks {
        /**
         * Called to determine whether the given position can be dismissed.
         * @param position
         * @param card
         * @return boolean
         */
        boolean canDismiss(int position, Card card);

        /**
         * Called when the user has indicated they she would like to dismiss one or more list item
         * positions.
         *
         * @param listView               The originating {@link ListContainer}.
         * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
         *                               order for convenience.
         */
        void onDismiss(ListContainer listView, int[] reverseSortedPositions);
    }


    public void animateDismissPosition(int position) {
        if (position > ListContainer.INVALID_INDEX)
            animateDismissPosition(Arrays.asList(position));
    }

    public void animateDismissPosition(final Collection<Integer> positions) {
        if (mBaseAdapter == null) {
            throw new IllegalStateException("Call setup method before animate!");
        }
        prepareAnimation();

        final List<Integer> positionsCopy = new ArrayList<Integer>(positions);
        List<CardViewWrapper> views = getVisibleViewsForPositions(positionsCopy);

        for (CardViewWrapper cardView : views) {
            dismissiCardWithAnimation(cardView);
        }
    }

    public void animateDismiss(Card card) {
        if (card != null)
            animateDismiss(Arrays.asList(card));
    }

    public void animateDismiss(Collection<Card> cards) {
        if (mBaseAdapter == null) {
            throw new IllegalStateException("Call setup method before animate!");
        }
        prepareAnimation();

        final List<Card> cardsCopy = new ArrayList<Card>(cards);
        List<CardViewWrapper> views = getVisibleViewsForCards(cardsCopy);

        for (CardViewWrapper cardView : views) {
            dismissiCardWithAnimation(cardView);
        }
    }

    /**
     * Prepare the animation
     */
    private void prepareAnimation() {
        setupCardListView();
        if (mCardListView != null) {
            mListWidth = mCardListView.getWidth();
        }
    }


    public abstract void animate(Card card, CardViewWrapper cardView);//, Card.OnDismissAnimationListener onDismissAnimationListener);


    /**
     * Retrieves the cardListView
     */
    private void setupCardListView() {

        if (mBaseAdapter != null) {
            if (mBaseAdapter instanceof CardArrayAdapter) {
                mCardListView = ((CardArrayAdapter) mBaseAdapter).getCardListView();
            }
        }

        if (mCardListView == null) {
            throw new IllegalStateException("BaseDismissAnimation works with a CardListView");
        }
    }


    /**
     * Returns the visible view for the positions
     *
     * @param positions
     * @return List<CardViewWrapper>
     */
    private List<CardViewWrapper> getVisibleViewsForPositions(final Collection<Integer> positions) {
        List<CardViewWrapper> views = new ArrayList<CardViewWrapper>();
        for (int i = 0; i < mCardListView.getChildCount(); i++) {
            Component child = mCardListView.getComponentAt(i);
            if (positions.contains(mCardListView.getChildIndex(child))) {
                views.add((CardViewWrapper) child);
            }
        }
        return views;
    }

    /**
     * Returns the visible view for the cards
     *
     * @param cardsCopy
     * @return List<CardViewWrapper>
     */
    private List<CardViewWrapper> getVisibleViewsForCards(List<Card> cardsCopy) {
        List<CardViewWrapper> originalViews = new ArrayList<CardViewWrapper>();
        for (Card card : cardsCopy) {
            originalViews.add(card.getCardView());
        }
        return originalViews;
    }

    private void dismissiCardWithAnimation(final CardViewWrapper cardView) {
        ++mDismissAnimationRefCount;
        animate(cardView.getCard(), cardView);
    }


    protected void invokeCallbak(final Component dismissView) {
        // Animate the dismissed list item to zero-height and fire the dismiss callback when
        // all dismissed list item animations have completed. This triggers layout on each animation
        // frame; in the future we may want to do something smarter and more performant.

        final int dismissPosition = mBaseAdapter.getPosition(((CardViewWrapper) dismissView).getCard());

        final ComponentContainer.LayoutConfig lp = dismissView.getLayoutConfig();
        final int originalHeight = dismissView.getHeight();

//        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);

        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setStateChangedListener(new AnimatorListenerAdapter() {
            @Override
            public void onEnd(Animator animator) {
                super.onEnd(animator);
                --mDismissAnimationRefCount;
                if (mDismissAnimationRefCount == 0) {
                    // No active animations, process all pending dismisses.
                    // Sort by descending position
                    Collections.sort(mPendingDismisses);

                    int[] dismissPositions = new int[mPendingDismisses.size()];
                    for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
                        dismissPositions[i] = mPendingDismisses.get(i).position;
                    }
                    mCallbacks.onDismiss(mCardListView, dismissPositions);

                    // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
                    // animation with a stale position
                    mDownPosition = ListContainer.INVALID_INDEX;

                    ComponentContainer.LayoutConfig lp;
                    for (PendingDismissData pendingDismiss : mPendingDismisses) {
                        // Reset view presentation
                        pendingDismiss.view.setAlpha(1f);
                        pendingDismiss.view.setTranslationX(0);
                        lp = pendingDismiss.view.getLayoutConfig();
                        lp.height = 0;
                        pendingDismiss.view.setLayoutConfig(lp);
                    }

                    // Send a cancel event
                    long time = Time.getRealTime();
                    //todo no such method
//                    MotionEvent cancelEvent = MotionEvent.obtain(time, time,
//                            MotionEvent.ACTION_CANCEL, 0, 0, 0);
//                    mCardListView.dispatchTouchEvent(cancelEvent);
                    mPendingDismisses.clear();
                }
            }
        });

        animatorValue.setValueUpdateListener((animatorValue1, v) -> {
            lp.height = (int) (lp.height - (lp.height - 1) * v);
            dismissView.setLayoutConfig(lp);
        });


        mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
        animatorValue.start();
    }


    class PendingDismissData implements Comparable<PendingDismissData> {
        public int position;
        public Component view;

        public PendingDismissData(int position, Component view) {
            this.position = position;
            this.view = view;
        }

        @Override
        public int compareTo(PendingDismissData other) {
            // Sort by descending position
            return other.position - position;
        }
    }


    DismissCallbacks mCallbacks = new DismissCallbacks() {

        @Override
        public boolean canDismiss(int position, Card card) {
            return card.isSwipeable();
        }

        @Override
        public void onDismiss(ListContainer listView, int[] reverseSortedPositions) {

            int[] itemPositions = new int[reverseSortedPositions.length];
            String[] itemIds = new String[reverseSortedPositions.length];
            int i = 0;

            //Remove cards and notifyDataSetChanged
            for (int position : reverseSortedPositions) {
                Card card = mBaseAdapter.getItem(position);
                if (card != null) {
                    itemPositions[i] = position;
                    itemIds[i] = card.getId();
                    i++;
                    mBaseAdapter.remove(card);
                    if (card.getOnSwipeListener() != null) {
                        card.getOnSwipeListener().onSwipe(card);
                    }
                }
            }
            mBaseAdapter.notifyDataChanged();

        }
    };


    private Animator createAnimatorForView(final Component view) {
        final ComponentContainer.LayoutConfig lp = view.getLayoutConfig();
        final int originalHeight = view.getHeight();

        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setStateChangedListener(new AnimatorListenerAdapter() {
            @Override
            public void onEnd(Animator animator) {
                super.onEnd(animator);
                lp.height = 0;
                view.setLayoutConfig(lp);
            }
        });
        animatorValue.setValueUpdateListener((animatorValue1, v) -> {
            lp.height = (int) ((1 - v) * originalHeight);
            view.setLayoutConfig(lp);
        });
        return animatorValue;
    }


    //--------------------------------------------------------------------------
    // Getters and setters
    //--------------------------------------------------------------------------

    public boolean isDismissRight() {
        return mDismissRight;
    }

    public void setDismissRight(boolean dismissRight) {
        mDismissRight = dismissRight;
    }

    public int getAnimationTime() {
        return mAnimationTime;
    }
}
